<!DOCTYPE HTML>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>Highcharts Example</title>

	<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
	<script type="text/javascript">
		/**
		 * This is a complex demo of how to set up a Highcharts chart, coupled to a
		 * dynamic source and extended by drawing image sprites, wind arrow paths
		 * and a second grid on top of the chart. The purpose of the demo is to inpire
		 * developers to go beyond the basic chart types and show how the library can
		 * be extended programmatically. This is what the demo does:
		 *
		 * - Loads weather forecast from www.yr.no in form of an XML service. The XML
		 *   is translated on the Higcharts website into JSONP for the sake of the demo
		 *   being shown on both our website and JSFiddle.
		 * - When the data arrives async, a Meteogram instance is created. We have
		 *   created the Meteogram prototype to provide an organized structure of the different
		 *   methods and subroutines associated with the demo.
		 * - The parseYrData method parses the data from www.yr.no into several parallel arrays. These
		 *   arrays are used directly as the data option for temperature, precipitation
		 *   and air pressure. As the temperature data gives only full degrees, we apply
		 *   some smoothing on the graph, but keep the original data in the tooltip.
		 * - After this, the options structure is build, and the chart generated with the
		 *   parsed data.
		 * - In the callback (on chart load), we weather icons on top of the temperature series.
		 *   The icons are sprites from a single PNG image, placed inside a clipped 30x30
		 *   SVG <g> element. VML interprets this as HTML images inside a clipped div.
		 * - Lastly, the wind arrows are built and added below the plot area, and a grid is
		 *   drawn around them. The wind arrows are basically drawn north-south, then rotated
		 *   as per the wind direction.
		 */

		function Meteogram(xml, container) {
			// Parallel arrays for the chart data, these are populated as the XML/JSON file
			// is loaded
			this.symbols = [];
			this.symbolNames = [];
			this.precipitations = [];
			this.windDirections = [];
			this.windDirectionNames = [];
			this.windSpeeds = [];
			this.windSpeedNames = [];
			this.temperatures = [];
			this.pressures = [];

			// Initialize
			this.xml = xml;
			this.container = container;

			// Run
			this.parseYrData();
		}
		/**
		 * Return weather symbol sprites as laid out at http://om.yr.no/forklaring/symbol/
		 */
		Meteogram.prototype.getSymbolSprites = function (symbolSize) {
			return {
				'01d': {
					x: 0,
					y: 0
				},
				'01n': {
					x: symbolSize,
					y: 0
				},
				'16': {
					x: 2 * symbolSize,
					y: 0
				},
				'02d': {
					x: 0,
					y: symbolSize
				},
				'02n': {
					x: symbolSize,
					y: symbolSize
				},
				'03d': {
					x: 0,
					y: 2 * symbolSize
				},
				'03n': {
					x: symbolSize,
					y: 2 * symbolSize
				},
				'17': {
					x: 2 * symbolSize,
					y: 2 * symbolSize
				},
				'04': {
					x: 0,
					y: 3 * symbolSize
				},
				'05d': {
					x: 0,
					y: 4 * symbolSize
				},
				'05n': {
					x: symbolSize,
					y: 4 * symbolSize
				},
				'18': {
					x: 2 * symbolSize,
					y: 4 * symbolSize
				},
				'06d': {
					x: 0,
					y: 5 * symbolSize
				},
				'06n': {
					x: symbolSize,
					y: 5 * symbolSize
				},
				'07d': {
					x: 0,
					y: 6 * symbolSize
				},
				'07n': {
					x: symbolSize,
					y: 6 * symbolSize
				},
				'08d': {
					x: 0,
					y: 7 * symbolSize
				},
				'08n': {
					x: symbolSize,
					y: 7 * symbolSize
				},
				'19': {
					x: 2 * symbolSize,
					y: 7 * symbolSize
				},
				'09': {
					x: 0,
					y: 8 * symbolSize
				},
				'10': {
					x: 0,
					y: 9 * symbolSize
				},
				'11': {
					x: 0,
					y: 10 * symbolSize
				},
				'12': {
					x: 0,
					y: 11 * symbolSize
				},
				'13': {
					x: 0,
					y: 12 * symbolSize
				},
				'14': {
					x: 0,
					y: 13 * symbolSize
				},
				'15': {
					x: 0,
					y: 14 * symbolSize
				},
				'20d': {
					x: 0,
					y: 15 * symbolSize
				},
				'20n': {
					x: symbolSize,
					y: 15 * symbolSize
				},
				'20m': {
					x: 2 * symbolSize,
					y: 15 * symbolSize
				},
				'21d': {
					x: 0,
					y: 16 * symbolSize
				},
				'21n': {
					x: symbolSize,
					y: 16 * symbolSize
				},
				'21m': {
					x: 2 * symbolSize,
					y: 16 * symbolSize
				},
				'22': {
					x: 0,
					y: 17 * symbolSize
				},
				'23': {
					x: 0,
					y: 18 * symbolSize
				}
			};
		};


		/**
		 * Function to smooth the temperature line. The original data provides only whole degrees,
		 * which makes the line graph look jagged. So we apply a running mean on it, but preserve
		 * the unaltered value in the tooltip.
		 */
		Meteogram.prototype.smoothLine = function (data) {
			var i = data.length,
					sum,
					value;

			while (i--) {
				data[i].value = value = data[i].y; // preserve value for tooltip

				// Set the smoothed value to the average of the closest points, but don't allow
				// it to differ more than 0.5 degrees from the given value
				sum = (data[i - 1] || data[i]).y + value + (data[i + 1] || data[i]).y;
				data[i].y = Math.max(value - 0.5, Math.min(sum / 3, value + 0.5));
			}
		};

		/**
		 * Callback function that is called from Highcharts on hovering each point and returns
		 * HTML for the tooltip.
		 */
		Meteogram.prototype.tooltipFormatter = function (tooltip) {

			// Create the header with reference to the time interval
			var index = tooltip.points[0].point.index,
					ret = '<small>' + Highcharts.dateFormat('%A, %b %e, %H:%M', tooltip.x) + '-' +
							Highcharts.dateFormat('%H:%M', tooltip.points[0].point.to) + '</small><br>';

			// Symbol text
			ret += '<b>' + this.symbolNames[index] + '</b>';

			ret += '<table>';

			// Add all series
			Highcharts.each(tooltip.points, function (point) {
				var series = point.series;
				ret += '<tr><td><span style="color:' + series.color + '">\u25CF</span> ' + series.name +
				': </td><td style="white-space:nowrap">' + Highcharts.pick(point.point.value, point.y) +
				series.options.tooltip.valueSuffix + '</td></tr>';
			});

			// Add wind
			ret += '<tr><td style="vertical-align: top">\u25CF Wind</td><td style="white-space:nowrap">' + this.windDirectionNames[index] +
			'<br>' + this.windSpeedNames[index] + ' (' +
			Highcharts.numberFormat(this.windSpeeds[index], 1) + ' m/s)</td></tr>';

			// Close
			ret += '</table>';


			return ret;
		};

		/**
		 * Draw the weather symbols on top of the temperature series. The symbols are sprites of a single
		 * file, defined in the getSymbolSprites function above.
		 */
		Meteogram.prototype.drawWeatherSymbols = function (chart) {
			var meteogram = this,
					symbolSprites = this.getSymbolSprites(30);

			$.each(chart.series[0].data, function (i, point) {
				var sprite,
						group;

				if (meteogram.resolution > 36e5 || i % 2 === 0) {

					sprite = symbolSprites[meteogram.symbols[i]];
					if (sprite) {

						// Create a group element that is positioned and clipped at 30 pixels width and height
						group = chart.renderer.g()
								.attr({
									translateX: point.plotX + chart.plotLeft - 15,
									translateY: point.plotY + chart.plotTop - 30,
									zIndex: 5
								})
								.clip(chart.renderer.clipRect(0, 0, 30, 30))
								.add();

						// Position the image inside it at the sprite position
						chart.renderer.image(
								'http://www.highcharts.com/samples/graphics/meteogram-symbols-30px.png',
								-sprite.x,
								-sprite.y,
								90,
								570
						)
								.add(group);
					}
				}
			});
		};

		/**
		 * Create wind speed symbols for the Beaufort wind scale. The symbols are rotated
		 * around the zero centerpoint.
		 */
		Meteogram.prototype.windArrow = function (name) {
			var level,
					path;

			// The stem and the arrow head
			path = [
				'M', 0, 7, // base of arrow
				'L', -1.5, 7,
				0, 10,
				1.5, 7,
				0, 7,
				0, -10 // top
			];

			level = $.inArray(name, ['Calm', 'Light air', 'Light breeze', 'Gentle breeze', 'Moderate breeze',
				'Fresh breeze', 'Strong breeze', 'Near gale', 'Gale', 'Strong gale', 'Storm',
				'Violent storm', 'Hurricane']);

			if (level === 0) {
				path = [];
			}

			if (level === 2) {
				path.push('M', 0, -8, 'L', 4, -8); // short line
			} else if (level >= 3) {
				path.push(0, -10, 7, -10); // long line
			}

			if (level === 4) {
				path.push('M', 0, -7, 'L', 4, -7);
			} else if (level >= 5) {
				path.push('M', 0, -7, 'L', 7, -7);
			}

			if (level === 5) {
				path.push('M', 0, -4, 'L', 4, -4);
			} else if (level >= 6) {
				path.push('M', 0, -4, 'L', 7, -4);
			}

			if (level === 7) {
				path.push('M', 0, -1, 'L', 4, -1);
			} else if (level >= 8) {
				path.push('M', 0, -1, 'L', 7, -1);
			}

			return path;
		};

		/**
		 * Draw the wind arrows. Each arrow path is generated by the windArrow function above.
		 */
		Meteogram.prototype.drawWindArrows = function (chart) {
			var meteogram = this;

			$.each(chart.series[0].data, function (i, point) {
				var sprite, arrow, x, y;

				if (meteogram.resolution > 36e5 || i % 2 === 0) {

					// Draw the wind arrows
					x = point.plotX + chart.plotLeft + 7;
					y = 255;
					if (meteogram.windSpeedNames[i] === 'Calm') {
						arrow = chart.renderer.circle(x, y, 10).attr({
							fill: 'none'
						});
					} else {
						arrow = chart.renderer.path(
								meteogram.windArrow(meteogram.windSpeedNames[i])
						).attr({
									rotation: parseInt(meteogram.windDirections[i], 10),
									translateX: x, // rotation center
									translateY: y // rotation center
								});
					}
					arrow.attr({
						stroke: (Highcharts.theme && Highcharts.theme.contrastTextColor) || 'black',
						'stroke-width': 1.5,
						zIndex: 5
					})
							.add();

				}
			});
		};

		/**
		 * Draw blocks around wind arrows, below the plot area
		 */
		Meteogram.prototype.drawBlocksForWindArrows = function (chart) {
			var xAxis = chart.xAxis[0],
					x,
					pos,
					max,
					isLong,
					isLast,
					i;

			for (pos = xAxis.min, max = xAxis.max, i = 0; pos <= max + 36e5; pos += 36e5, i++) {

				// Get the X position
				isLast = pos === max + 36e5;
				x = Math.round(xAxis.toPixels(pos)) + (isLast ? 0.5 : -0.5);

				// Draw the vertical dividers and ticks
				if (this.resolution > 36e5) {
					isLong = pos % this.resolution === 0;
				} else {
					isLong = i % 2 === 0;
				}
				chart.renderer.path(['M', x, chart.plotTop + chart.plotHeight + (isLong ? 0 : 28),
					'L', x, chart.plotTop + chart.plotHeight + 32, 'Z'])
						.attr({
							'stroke': chart.options.chart.plotBorderColor,
							'stroke-width': 1
						})
						.add();
			}
		};

		/**
		 * Get the title based on the XML data
		 */
		Meteogram.prototype.getTitle = function () {
			return 'Meteogram for ' + this.xml.location.name + ', ' + this.xml.location.country;
		};

		/**
		 * Build and return the Highcharts options structure
		 */
		Meteogram.prototype.getChartOptions = function () {
			var meteogram = this;

			return {
				chart: {
					renderTo: this.container,
					marginBottom: 70,
					marginRight: 40,
					marginTop: 50,
					plotBorderWidth: 1,
					width: 800,
					height: 310
				},

				title: {
					text: this.getTitle(),
					align: 'left'
				},

				credits: {
					text: 'Forecast from <a href="http://yr.no">yr.no</a>',
					href: this.xml.credit.link['@attributes'].url,
					position: {
						x: -40
					}
				},

				tooltip: {
					shared: true,
					useHTML: true,
					formatter: function () {
						return meteogram.tooltipFormatter(this);
					}
				},

				xAxis: [{ // Bottom X axis
					type: 'datetime',
					tickInterval: 2 * 36e5, // two hours
					minorTickInterval: 36e5, // one hour
					tickLength: 0,
					gridLineWidth: 1,
					gridLineColor: (Highcharts.theme && Highcharts.theme.background2) || '#F0F0F0',
					startOnTick: false,
					endOnTick: false,
					minPadding: 0,
					maxPadding: 0,
					offset: 30,
					showLastLabel: true,
					labels: {
						format: '{value:%H}'
					}
				}, { // Top X axis
					linkedTo: 0,
					type: 'datetime',
					tickInterval: 24 * 3600 * 1000,
					labels: {
						format: '{value:<span style="font-size: 12px; font-weight: bold">%a</span> %b %e}',
						align: 'left',
						x: 3,
						y: -5
					},
					opposite: true,
					tickLength: 20,
					gridLineWidth: 1
				}],

				yAxis: [{ // temperature axis
					title: {
						text: null
					},
					labels: {
						format: '{value}°',
						style: {
							fontSize: '10px'
						},
						x: -3
					},
					plotLines: [{ // zero plane
						value: 0,
						color: '#BBBBBB',
						width: 1,
						zIndex: 2
					}],
					// Custom positioner to provide even temperature ticks from top down
					tickPositioner: function () {
						var max = Math.ceil(this.max) + 1,
								pos = max - 12, // start
								ret;

						if (pos < this.min) {
							ret = [];
							while (pos <= max) {
								ret.push(pos++);
							}
						} // else return undefined and go auto

						return ret;

					},
					maxPadding: 0.3,
					tickInterval: 1,
					gridLineColor: (Highcharts.theme && Highcharts.theme.background2) || '#F0F0F0'

				}, { // precipitation axis
					title: {
						text: null
					},
					labels: {
						enabled: false
					},
					gridLineWidth: 0,
					tickLength: 0

				}, { // Air pressure
					allowDecimals: false,
					title: { // Title on top of axis
						text: 'hPa',
						offset: 0,
						align: 'high',
						rotation: 0,
						style: {
							fontSize: '10px',
							color: Highcharts.getOptions().colors[2]
						},
						textAlign: 'left',
						x: 3
					},
					labels: {
						style: {
							fontSize: '8px',
							color: Highcharts.getOptions().colors[2]
						},
						y: 2,
						x: 3
					},
					gridLineWidth: 0,
					opposite: true,
					showLastLabel: false
				}],

				legend: {
					enabled: false
				},

				plotOptions: {
					series: {
						pointPlacement: 'between'
					}
				},


				series: [{
					name: 'Temperature',
					data: this.temperatures,
					type: 'spline',
					marker: {
						enabled: false,
						states: {
							hover: {
								enabled: true
							}
						}
					},
					tooltip: {
						valueSuffix: '°C'
					},
					zIndex: 1,
					color: '#FF3333',
					negativeColor: '#48AFE8'
				}, {
					name: 'Precipitation',
					data: this.precipitations,
					type: 'column',
					color: '#68CFE8',
					yAxis: 1,
					groupPadding: 0,
					pointPadding: 0,
					borderWidth: 0,
					shadow: false,
					dataLabels: {
						enabled: true,
						formatter: function () {
							if (this.y > 0) {
								return this.y;
							}
						},
						style: {
							fontSize: '8px'
						}
					},
					tooltip: {
						valueSuffix: 'mm'
					}
				}, {
					name: 'Air pressure',
					color: Highcharts.getOptions().colors[2],
					data: this.pressures,
					marker: {
						enabled: false
					},
					shadow: false,
					tooltip: {
						valueSuffix: ' hPa'
					},
					dashStyle: 'shortdot',
					yAxis: 2
				}]
			}
		};

		/**
		 * Post-process the chart from the callback function, the second argument to Highcharts.Chart.
		 */
		Meteogram.prototype.onChartLoad = function (chart) {

			this.drawWeatherSymbols(chart);
			this.drawWindArrows(chart);
			this.drawBlocksForWindArrows(chart);

		};

		/**
		 * Create the chart. This function is called async when the data file is loaded and parsed.
		 */
		Meteogram.prototype.createChart = function () {
			var meteogram = this;
			this.chart = new Highcharts.Chart(this.getChartOptions(), function (chart) {
				meteogram.onChartLoad(chart);
			});
		};

		/**
		 * Handle the data. This part of the code is not Highcharts specific, but deals with yr.no's
		 * specific data format
		 */
		Meteogram.prototype.parseYrData = function () {

			var meteogram = this,
					xml = this.xml,
					pointStart;

			if (!xml || !xml.forecast) {
				$('#loading').html('<i class="fa fa-frown-o"></i> Failed loading data, please try again later');
				return;
			}

			// The returned xml variable is a JavaScript representation of the provided XML,
			// generated on the server by running PHP simple_load_xml and converting it to
			// JavaScript by json_encode.
			$.each(xml.forecast.tabular.time, function (i, time) {
				// Get the times - only Safari can't parse ISO8601 so we need to do some replacements
				var from = time['@attributes'].from + ' UTC',
						to = time['@attributes'].to + ' UTC';

				from = from.replace(/-/g, '/').replace('T', ' ');
				from = Date.parse(from);
				to = to.replace(/-/g, '/').replace('T', ' ');
				to = Date.parse(to);

				if (to > pointStart + 4 * 24 * 36e5) {
					return;
				}

				// If it is more than an hour between points, show all symbols
				if (i === 0) {
					meteogram.resolution = to - from;
				}

				// Populate the parallel arrays
				meteogram.symbols.push(time.symbol['@attributes']['var'].match(/[0-9]{2}[dnm]?/)[0]);
				meteogram.symbolNames.push(time.symbol['@attributes'].name);

				meteogram.temperatures.push({
					x: from,
					y: parseInt(time.temperature['@attributes'].value),
					// custom options used in the tooltip formatter
					to: to,
					index: i
				});

				meteogram.precipitations.push({
					x: from,
					y: parseFloat(time.precipitation['@attributes'].value)
				});
				meteogram.windDirections.push(parseFloat(time.windDirection['@attributes'].deg));
				meteogram.windDirectionNames.push(time.windDirection['@attributes'].name);
				meteogram.windSpeeds.push(parseFloat(time.windSpeed['@attributes'].mps));
				meteogram.windSpeedNames.push(time.windSpeed['@attributes'].name);

				meteogram.pressures.push({
					x: from,
					y: parseFloat(time.pressure['@attributes'].value)
				});

				if (i == 0) {
					pointStart = (from + to) / 2;
				}
			});

			// Smooth the line
			this.smoothLine(this.temperatures);

			// Create the chart when the data is loaded
			this.createChart();
		};
		// End of the Meteogram protype


		$(function () { // On DOM ready...

			// Set the hash to the yr.no URL we want to parse
			if (!location.hash) {
				var place = 'United_Kingdom/England/London';
				//place = 'France/Rhône-Alpes/Val_d\'Isère~2971074';
				//place = 'Norway/Sogn_og_Fjordane/Vik/Målset';
				//place = 'United_States/California/San_Francisco';
				//place = 'United_States/Minnesota/Minneapolis';
				location.hash = 'http://www.yr.no/place/' + place + '/forecast_hour_by_hour.xml';

			}

			// Then get the XML file through Highcharts' jsonp provider, see
			// https://github.com/highslide-software/highcharts.com/blob/master/samples/data/jsonp.php
			// for source code.
			$.getJSON(
					'http://www.highcharts.com/samples/data/jsonp.php?url=' + location.hash.substr(1) + '&callback=?',
					function (xml) {
						var meteogram = new Meteogram(xml, 'container');
					}
			);

		});
	</script>
</head>
<body>

<script src="../../js/highcharts.js"></script>
<script src="../../js/modules/exporting.js"></script>
<link href="http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">

<div id="container" style="width: 800px; height: 310px; margin: 0 auto">
	<div style="margin-top: 100px; text-align: center" id="loading">
		<i class="fa fa-spinner fa-spin"></i> Loading data from external source
	</div>
</div>
<!--
<div style="width: 800px; margin: 0 auto">
	<a href="#http://www.yr.no/place/United_Kingdom/England/London/forecast_hour_by_hour.xml">London</a>,
	<a href="#http://www.yr.no/place/France/Rhône-Alpes/Val_d\'Isère~2971074/forecast_hour_by_hour.xml">Val d'Isère</a>,
	<a href="#http://www.yr.no/place/United_States/California/San_Francisco/forecast_hour_by_hour.xml">San Francisco</a>,
	<a href="#http://www.yr.no/place/Norway/Vik/Vikafjell/forecast_hour_by_hour.xml">Vikjafjellet</a>
</div>
-->
</body>
</html>
